package image.lines;

import image.processing.ImageProcessor;
import image.utils.ImageUtilitiesImpl;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Hough {
	private double minRho;
	private double maxRho;
	private double maxTheta = Math.PI / 2;
	private double minTheta = -Math.PI / 2;
	private int discretizationRho;
	private int discetizationTheta;
	private BufferedImage src;
	private int acumulator[][];
	private double[] discretizatedRho;
	private double[] discretizatedTheta;
	private float epsilon;
	private int percent;

	public Hough(int discretizationRho, int discetizationTheta, float epsilon,
			BufferedImage src, int percent) {
		this.discetizationTheta = discetizationTheta;
		this.discretizationRho = discretizationRho;
		int max = Math.max(src.getHeight(), src.getWidth());
		this.minRho = -Math.sqrt(2) * max;
		this.maxRho = Math.sqrt(2) * max;
		this.src = ImageProcessor.threshold(src, 128);
		this.epsilon = epsilon;
		this.percent = percent;
		this.discretizatedRho = new double[discretizationRho];
		this.discretizatedTheta = new double[discetizationTheta];
		this.acumulator = new int[discretizationRho][discetizationTheta];
		double ant = this.minRho;
		for (int i = 0; i < this.discretizatedRho.length; i++) {
			this.discretizatedRho[i] = ant;
			ant += ((2 * Math.sqrt(2) * max) / (discretizationRho-1));
		}
		ant = minTheta;
		for (int i = 0; i < this.discretizatedTheta.length; i++) {
			this.discretizatedTheta[i] = ant;
			ant += (Math.PI) / (discetizationTheta-1);
		}
		
	}

	private void printArray(double[] array){
		System.out.print("[ ");
		for (double i: array){
			System.out.print(i + " ");
		}
		System.out.println("]");
	}

	public BufferedImage findLines() {
		WritableRaster raster = src.getRaster();
		System.out.println("rho:");
		printArray(this.discretizatedRho);
		System.out.println("theta:");
		printArray(this.discretizatedTheta);
		for (int x = 0; x < src.getWidth(); x++) {
			for (int y = 0; y < src.getHeight(); y++) {
				int sample = raster.getSample(x, y, 0);
				if (sample == 255) { //cambiarrrrrrrrrr
					satisfiesLinesEquation(x, y);
				}
			}
		}
		Set<int[]> maximos = getLocalMaximuns(this.percent); //
		for(int[] maximo: maximos){
			System.out.println("rho_i: " + maximo[0] + "theta_i" + maximo[1] + " acum: " + acumulator[maximo[0]][maximo[1]]);
			System.out.println("rho: " + discretizatedRho[maximo[0]] + "theta: " + discretizatedTheta[maximo[1]]);

		}
		return drawLines2(maximos);
		

	}

	private void satisfiesLinesEquation(int x, int y) {
		for (int rho = 0; rho < discretizatedRho.length; rho++) {
			for (int theta = 0; theta < discretizatedTheta.length; theta++) {
				double value = Math.abs(discretizatedRho[rho] - x
						* Math.cos(discretizatedTheta[theta]) - y
						* Math.sin(discretizatedTheta[theta]));
				if (value < epsilon) {
					acumulator[rho][theta] += 1;
				}
			}
		}
	}

	private int[] getMax() {
		int max = Integer.MIN_VALUE;
		int[] resp = new int[2];
		for (int rho = 0; rho < acumulator.length; rho++) {
			for (int theta = 0; theta < acumulator[rho].length; theta++) {
				if(acumulator[rho][theta] > max){
					max = acumulator[rho][theta];
					resp[0] = rho;
					resp[1] = theta;
				}
			}
		}
		return resp;
	}
	private Set<int[]>findMaxLocations(){
		int[] maxLocation = getMax();
		int max = this.acumulator[maxLocation[0]][maxLocation[1]];
		Set<int[]> resp = new HashSet<int[]>();
		for (int rho = 0; rho < acumulator.length; rho++) {
			for (int theta = 0; theta < acumulator[rho].length; theta++) {
				if(acumulator[rho][theta] == max){
					max = acumulator[rho][theta];
					int[] values = new int[2];
					values[0] = rho;
					values[1] = theta;
					resp.add(values);
				}
			}
		}
		return resp;
	}
	/*
	 * finds maximuns percent minor than the maximun value of the acumulator;
	 * percent 0-100
	 */
	private Set<int[]> getLocalMaximuns(int percent) {
		Set<int[]> resp = findMaxLocations();
		int[] maxLocation = resp.iterator().next();
		int max = acumulator[maxLocation[0]][maxLocation[1]];
		System.out.println("max: " + max);
		if(percent == 0){
			return resp;
		}
		double tolerance = max - ((double)max * percent) / 100;
		System.out.println("tole : " + tolerance);
		for (int rho = 0; rho < acumulator.length; rho++) {
			for (int theta = 0; theta < acumulator[rho].length; theta++) {
				if(acumulator[rho][theta] > tolerance){
					max = acumulator[rho][theta];
					int values[] = new int[2];
					values[0] = rho;
					values[1] = theta;
					resp.add(values);
				}
			}
		}
		return resp;
	}
	
	private BufferedImage drawLines(Set<int[]> maximos){
		BufferedImage resp = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_ARGB);
		WritableRaster raster = src.getRaster();
		for(int x = 0; x < resp.getWidth(); x++){
			for(int y = 0; y < resp.getHeight(); y++){
				for(int[] maximo: maximos){
					if(isInLine(x, y, maximo)){
						resp.getRaster().setSample(x, y, 0, 255);
					}else{
						resp.getRaster().setSample(x, y, 0,raster.getSample(x, y, 0)); //o el valor original?
					}
				}
			}
		}
		return resp;
	}

	private BufferedImage drawLines2(Set<int[]> maximos){
		
		BufferedImage resp = ImageUtilitiesImpl.getInstance().toRGB(src);
		WritableRaster raster = resp.getRaster();
		for(int[] maximo: maximos){
			int[][] aux = getLinePoints(maximo);
			for(int i = 0; i < aux[0].length; i++){
				raster.setSample(aux[0][i], aux[1][i], 0, 0);
				raster.setSample(aux[0][i], aux[1][i], 1, 0);
				raster.setSample(aux[0][i], aux[1][i], 2, 255);
			}
		}
		return resp;
	}
	
	private BufferedImage drawLines3(Set<int[]> maximos){
		
		BufferedImage resp = ImageUtilitiesImpl.getInstance().toRGB(src);
		WritableRaster raster = resp.getRaster();
		Graphics2D g2 = (Graphics2D) resp.getGraphics();

		for(int[] maximo: maximos){
			int[][] aux = getLinePoints(maximo);
			for(int i = 0; i < aux[0].length; i++){
				raster.setSample(aux[0][i], aux[1][i], 0, 0);
				raster.setSample(aux[0][i], aux[1][i], 1, 0);
				raster.setSample(aux[0][i], aux[1][i], 2, 255);
			}
		}
		return resp;
	}
	
	
	private int[][] getLinePoints(int[] maximo){
		List<Integer> xs = new ArrayList<Integer>();
		List<Integer> ys = new ArrayList<Integer>(); 
		for(int x = 0; x < src.getWidth(); x++){
			for(int y = 0; y < src.getHeight(); y++){
				double firstAdder = (x * Math.cos(discretizatedTheta[maximo[1]]));
				double secondAdder = (y * Math.sin(discretizatedTheta[maximo[1]]));
				double point = (firstAdder + secondAdder - discretizatedRho[maximo[0]]);
				double ERROR = 0.5d;
				if( Math.abs(point) < ERROR){
					xs.add(x);
					ys.add(y);
				}
			}
		}
		int [][]resp = new int[2][xs.size()];
		for( int i=0; i < xs.size(); i++){
			resp[0][i] = xs.get(i);
			resp[1][i] = ys.get(i);
		}
		return resp;
	}
	
	private boolean isInLine(int x,int y, int[] maximos){
		double firstAdder = (x * Math.cos(discretizatedTheta[maximos[1]]));
		double secondAdder = (y * Math.sin(discretizatedTheta[maximos[1]]));
		double point = (firstAdder + secondAdder - discretizatedRho[maximos[0]]);
		double ERROR = epsilon;
		if( Math.abs(firstAdder + secondAdder - discretizatedRho[maximos[0]]) < ERROR){
			//System.out.println(point);
			return true;
		}
		return false;
	}
	 
}
